// UsbEject version 1.0 March 2006
// written by Simon Mourier <email: simon [underscore] mourier [at] hotmail [dot] com>

namespace UsbEject.Library
{
	using System;
	using System.Collections.Generic;
	using System.ComponentModel;
	using System.Runtime.InteropServices;
	using System.Text;
	
    /// <summary>
    /// A generic base class for physical device classes.
    /// </summary>
    public abstract class DeviceClass : IDisposable
	{
		private IntPtr deviceInfoSet;
		private Guid classGuid;
        private List<Device> devices;
		private bool alreadyDisposed;

		protected DeviceClass(Guid classGuid)
			: this(classGuid, IntPtr.Zero)
		{
		}

        internal virtual Device CreateDevice(DeviceClass deviceClass, Native.SP_DEVINFO_DATA deviceInfoData, string path, int index)
        {
            return new Device(deviceClass, deviceInfoData, path, index);
        }

        /// <summary>
        /// Initializes a new instance of the DeviceClass class.
        /// </summary>
        /// <param name="classGuid">A device class Guid.</param>
        /// <param name="hwndParent">The handle of the top-level window to be used for any user interface or IntPtr.Zero for no handle.</param>
		protected DeviceClass(Guid deviceID, IntPtr hwndParent)
		{
			this.classGuid = deviceID;

			this.deviceInfoSet = Native.SetupDiGetClassDevs(ref this.classGuid, 0, hwndParent, Native.DIGCF_DEVICEINTERFACE | Native.DIGCF_PRESENT);
			if (this.deviceInfoSet.ToInt32() == Native.INVALID_HANDLE_VALUE)
			{
				throw new Win32Exception(Marshal.GetLastWin32Error());
			}
		}

		#region IDisposable

		~DeviceClass()
		{
			this.Dispose(false);
		}

		// Implementation of IDisposable.
		// Call the virtual Dispose method.
		// Suppress Finalization.
		public void Dispose()
		{
			this.Dispose(true);
			GC.SuppressFinalize(this);
		}

		protected virtual void Dispose(bool isDisposing)
		{
			if (!this.alreadyDisposed)
			{
				if (this.deviceInfoSet != IntPtr.Zero)
				{
					Native.SetupDiDestroyDeviceInfoList(this.deviceInfoSet);
					this.deviceInfoSet = IntPtr.Zero;
				}

				this.alreadyDisposed = true;
			}
		}

		#endregion IDisposable

		/// <summary>
        /// Gets the device class's guid.
        /// </summary>
		public Guid ClassGuid
		{
			get
			{
				return this.classGuid;
			}
		}

        /// <summary>
        /// Gets the list of devices of this device class.
        /// </summary>
        public List<Device> Devices
        {
            get
            {
				if (this.devices == null)
                {
					this.devices = new List<Device>();
			        int index = 0;
                    while (true)
                    {
                        Native.SP_DEVICE_INTERFACE_DATA interfaceData = new Native.SP_DEVICE_INTERFACE_DATA();

						if (!Native.SetupDiEnumDeviceInterfaces(this.deviceInfoSet, null, ref this.classGuid, index, interfaceData))
                        {
                            int error = Marshal.GetLastWin32Error();
							if (error != Native.ERROR_NO_MORE_ITEMS)
							{
								throw new Win32Exception(error);
							}

                            break;
                        }

                        Native.SP_DEVINFO_DATA devData = new Native.SP_DEVINFO_DATA();
                        int size = 0;
						if (!Native.SetupDiGetDeviceInterfaceDetail(this.deviceInfoSet, interfaceData, IntPtr.Zero, 0, ref size, devData))
                        {
                            int error = Marshal.GetLastWin32Error();
							if (error != Native.ERROR_INSUFFICIENT_BUFFER)
							{
								throw new Win32Exception(error);
							}
                        }

                        IntPtr buffer = Marshal.AllocHGlobal(size);
                        Native.SP_DEVICE_INTERFACE_DETAIL_DATA detailData = new Native.SP_DEVICE_INTERFACE_DETAIL_DATA();
                        detailData.cbSize = Marshal.SizeOf(typeof(Native.SP_DEVICE_INTERFACE_DETAIL_DATA));
                        Marshal.StructureToPtr(detailData, buffer, false);

						if (!Native.SetupDiGetDeviceInterfaceDetail(this.deviceInfoSet, interfaceData, buffer, size, ref size, devData))
                        {
                            Marshal.FreeHGlobal(buffer);
                            throw new Win32Exception(Marshal.GetLastWin32Error());
                        }

                        IntPtr pDevicePath = (IntPtr)((int)buffer + Marshal.SizeOf(typeof(int)));
                        string devicePath = Marshal.PtrToStringAuto(pDevicePath);
                        Marshal.FreeHGlobal(buffer);

						Device device = this.CreateDevice(this, devData, devicePath, index);
						this.devices.Add(device);

                        index++;
                    }

					this.devices.Sort();
                }

				return this.devices;
            }
        }

        internal Native.SP_DEVINFO_DATA GetInfo(int dnDevInst)
        {
            StringBuilder sb = new StringBuilder(1024);
            int hr = Native.CM_Get_Device_ID(dnDevInst, sb, sb.Capacity, 0);
            if (hr != 0)
                throw new Win32Exception(hr);

            Native.SP_DEVINFO_DATA devData = new Native.SP_DEVINFO_DATA();
            devData.cbSize = Marshal.SizeOf(typeof(Native.SP_DEVINFO_DATA));
			if (!Native.SetupDiOpenDeviceInfo(this.deviceInfoSet, sb.ToString(), IntPtr.Zero, 0, devData))
			{
				throw new Win32Exception(Marshal.GetLastWin32Error());
			}

            return devData;
        }

        internal string GetProperty(Native.SP_DEVINFO_DATA devData, int property, string defaultValue)
        {
            if (devData == null)
                throw new ArgumentNullException("devData");

            int propertyRegDataType = 0;
            int requiredSize;
            int propertyBufferSize = 1024;

            IntPtr propertyBuffer = Marshal.AllocHGlobal(propertyBufferSize);
			if (!Native.SetupDiGetDeviceRegistryProperty(this.deviceInfoSet,
                devData,
                property,
                out propertyRegDataType,
                propertyBuffer,
                propertyBufferSize,
                out requiredSize))
            {
                Marshal.FreeHGlobal(propertyBuffer);
                int error = Marshal.GetLastWin32Error();
                if (error != Native.ERROR_INVALID_DATA)
                    throw new Win32Exception(error);
                return defaultValue;
            }

            string value = Marshal.PtrToStringAuto(propertyBuffer);
            Marshal.FreeHGlobal(propertyBuffer);
            return value;
        }

        internal int GetProperty(Native.SP_DEVINFO_DATA devData, int property, int defaultValue)
        {
            if (devData == null)
                throw new ArgumentNullException("devData");

            int propertyRegDataType = 0;
            int requiredSize;
            int propertyBufferSize = Marshal.SizeOf(typeof(int));

            IntPtr propertyBuffer = Marshal.AllocHGlobal(propertyBufferSize);
			if (!Native.SetupDiGetDeviceRegistryProperty(this.deviceInfoSet,
                devData,
                property,
                out propertyRegDataType,
                propertyBuffer,
                propertyBufferSize,
                out requiredSize))
            {
                Marshal.FreeHGlobal(propertyBuffer);
                int error = Marshal.GetLastWin32Error();
                if (error != Native.ERROR_INVALID_DATA)
                    throw new Win32Exception(error);
                return defaultValue;
            }

            int value = (int)Marshal.PtrToStructure(propertyBuffer, typeof(int));
            Marshal.FreeHGlobal(propertyBuffer);
            return value;
        }

        internal Guid GetProperty(Native.SP_DEVINFO_DATA devData, int property, Guid defaultValue)
        {
			if (devData == null)
			{
				throw new ArgumentNullException("devData");
			}

            int propertyRegDataType = 0;
            int requiredSize;
            int propertyBufferSize = Marshal.SizeOf(typeof(Guid));

            IntPtr propertyBuffer = Marshal.AllocHGlobal(propertyBufferSize);
			if (!Native.SetupDiGetDeviceRegistryProperty(this.deviceInfoSet,
                devData,
                property,
                out propertyRegDataType,
                propertyBuffer,
                propertyBufferSize,
                out requiredSize))
            {
                Marshal.FreeHGlobal(propertyBuffer);
                int error = Marshal.GetLastWin32Error();
				if (error != Native.ERROR_INVALID_DATA)
				{
					throw new Win32Exception(error);
				}

                return defaultValue;
            }

            Guid value = (Guid)Marshal.PtrToStructure(propertyBuffer, typeof(Guid));
            Marshal.FreeHGlobal(propertyBuffer);
            return value;
        }
	}
}
